package com.shujia

import org.junit.{Before, Test}

import scala.io.{BufferedSource, Source}

class Demo17StudentExercise {
  case class Stu(id: String, name: String, age: Int, gender: String, clazz: String)

  case class Sco(id: String, subjectId: String, score: Int)

  case class Subject(subjectId: String, subjectName: String, subjectSco: Int)

  var student: List[Stu] = _
  var score: List[Sco] = _
  var subject: List[Subject] = _

  var stuMap: Map[String, String] = _

  var sumScore: Map[String, Int] = _

  @Before
  def init(): Unit = {
    // 分别读取学生、分数、科目数据，并将每一条数据构建成对应的样例类对象，最终形成一个List
    val stuBS: BufferedSource = Source.fromFile("data/stu/students.txt")
    val scoBS: BufferedSource = Source.fromFile("data/stu/score.txt")
    val subBS: BufferedSource = Source.fromFile("data/stu/subject.txt")


    student = stuBS.getLines().map(line => {
      val splits: Array[String] = line.split(",")
      Stu(splits(0), splits(1), splits(2).toInt, splits(3), splits(4))
    }).toList

    score = scoBS.getLines().map(line => {
      val splits: Array[String] = line.split(",")
      Sco(splits(0), splits(1), splits(2).toInt)
    }).toList

    subject = subBS.getLines().map(line => {
      val splits: Array[String] = line.split(",")
      Subject(splits(0), splits(1), splits(2).toInt)
    }).toList

    // 基于学生和科目数据 构建Map 用于和分数数据进行关联
    stuMap = student
      .map(stu => (stu.id, s"${stu.name},${stu.clazz}"))
      .toMap

    sumScore = score
      .map(sco => (sco.id, sco.score))
      .groupBy(t2 => t2._1)
      // 在groupBy之后，如果直接使用map方法并且返回的是二元组，那么会得到一个Map类型
      .map(kv => (kv._1, kv._2.map(_._2).sum))


    stuBS.close()
    scoBS.close()
    subBS.close()

  }

  @Test
  def clazzCnt(): Unit = {
    // 1、统计班级人数 [班级,人数]

    student
      .map(_.clazz)
      .groupBy(clazz => clazz)
      .map(kv => s"${kv._1},${kv._2.size}")
      .foreach(println)

  }

  @Test
  // 统计学生的总分 [学号,学生姓名,学生年龄,总分]
  def sumStuScore(): Unit = {

    // 计算每个学生的总分
    //    val sumScoMap: Map[String, Int] = score
    //      .map(sco => (sco.id, sco.score))
    //      .groupBy(t2 => t2._1)
    //      // 在groupBy之后，如果直接使用map方法并且返回的是二元组，那么会得到一个Map类型
    //      .map(kv => {
    //        val id: String = kv._1
    //        val idAndScoList: List[(String, Int)] = kv._2
    //        val sumSco: Int = idAndScoList.map(idAndSco => idAndSco._2).sum
    //        (id, sumSco)
    //      })
    //      .toMap

    // 遍历每一条学生数据，取出id，去sumScoMap中以id作为key获取value即总分
    student
      .map(stu => {
        val sumSco: Int = sumScore.getOrElse(stu.id, 0)
        s"${stu.id},${stu.name},${stu.age},$sumSco"
      })
      .foreach(println)
  }

  def filterWithIdsAndJoin(ids: List[String]): Unit = {
    val subMap: Map[String, String] = subject
      .map(sub => (sub.subjectId, sub.subjectName))
      .toMap

    // 遍历分数数据 基于top10IdList 进行过滤
    score
      .filter(sco => ids.contains(sco.id))
      .map(sco => {
        s"${sco.id},${stuMap.getOrElse(sco.id, "")},${subMap.getOrElse(sco.subjectId, "")},${sco.score}"
      }).foreach(println)

  }

  @Test
  // 1、统计年级排名前十学生各科的分数 [学号, 姓名，班级，科目，分数]
  def question01(): Unit = {
    // 统计学生总分
    val top10IdList: List[String] = sumScore
      .toList
      // 按照分数降序排列
      .sortBy(-_._2)
      // 取top10
      .take(10)
      // 提取id
      .map(_._1)


    filterWithIdsAndJoin(top10IdList)

  }

  @Test
  // 2、统计总分大于年级平均分的学生 [学号，姓名，班级，总分]
  def question02(): Unit = {
    // 统计年级平均分
    val avgScore: Double = score
      .map(_.score)
      .sum / student.size.toDouble

    println(avgScore)


    sumScore
      // 基于年级平均分进行过滤
      .filter(_._2 > avgScore)
      .map(t2 => s"${t2._1},${stuMap.getOrElse(t2._1, "")},${t2._2}")
      .foreach(println)

  }

  @Test
  // 3、统计每科都及格的学生 [学号，姓名，班级，科目，分数]
  def question03(): Unit = {
    // 以科目id作为key，科目总分作为value，构建Map用于关联
    val subScoreMap: Map[String, Int] = subject
      .map(sub => (sub.subjectId, sub.subjectSco))
      .toMap

    val allPassIdList: List[String] = score
      .filter(sco => {
        sco.score >= (subScoreMap.getOrElse(sco.subjectId, 0) * 0.6)
      })
      // 统计每个学生及格的科目数
      .groupBy(_.id)
      .map(kv => (kv._1, kv._2.size))
      .filter(_._2 == 6)
      .keys
      .toList

    filterWithIdsAndJoin(allPassIdList)
  }

  @Test
  // 4、统计每个班级的前三名 [学号，姓名，班级，分数]
  def question04(): Unit = {
    student
      .map(stu => {
        (stu.id, stu.name, stu.clazz, sumScore.getOrElse(stu.id, 0))
      })
      // 按照班级分组
      .groupBy(_._3)
      .flatMap(kv => {
        val clazz: String = kv._1
        kv._2.sortBy(-_._4).take(3)
      })
      .map(t4 => s"${t4._1},${t4._2},${t4._3},${t4._4}")
      .foreach(println)
  }

  @Test
  // 5、统计偏科最严重的前100名学生  [学号，姓名，班级，科目，分数]
  def question05(): Unit = {

    // 关联科目的数据 将科目成绩转换成百分制

    // 以科目id作为key，科目总分作为value，构建Map用于关联
    val subScoreMap: Map[String, Int] = subject
      .map(sub => (sub.subjectId, sub.subjectSco))
      .toMap

    val idList: List[String] = score
      .map(sco => (sco.id, sco.score * 100.toDouble / subScoreMap.getOrElse(sco.subjectId, 0)))
      .groupBy(_._1)
      .map(kv => {
        val id: String = kv._1
        // 计算平均分
        val avgSco: Double = kv._2.map(_._2).sum / kv._2.size
        val variance: Double = kv._2.map(t2 => Math.pow(t2._2 - avgSco, 2)).sum / kv._2.size
        (id, variance)
      })
      .toList
      .sortBy(-_._2)
      .take(100)
      .map(_._1)

    filterWithIdsAndJoin(idList)


  }


}
